﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using NHibernate.Dialect;
using System.Data.Common;
using NHibernate;
using NHibernate.Mapping;

namespace Apollo.Setup
{
    public static class SetupHepler
    {
        public static void ExecuteSetupAction(HttpContext context, SetupAction action) {
            Apollo.Application.Current.Initialize();

            switch (action) {
                case SetupAction.CreateOrAlterTables: CreateOrAlterTables(context); break;
                case SetupAction.GenerateEntityMappings: GenerateEntityMappings(context); break;
                case SetupAction.GenerateEntityRelations: GenerateEntityRelations(context); break;
                case SetupAction.CreateDefaultLanguage: CreateDefaultLanguage(context); break;
                case SetupAction.CreateAdminUser: CreateAdminUser(context); break;
                case SetupAction.CreateAdvancedEntityManagement: CreateAdvancedEntityManagement(context); break;
                case SetupAction.GenerateSettingsMenus: GenerateSettingsMenus(context); break;
                default:
                    context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action not found"" }");
                    break;
            }
        }

        public static void CreateOrAlterTables(HttpContext context)
        {
            NHibernate.Cfg.Configuration config = Apollo.Application.Configuration;

            Dialect dialect = Dialect.GetDialect(config.Properties);

            string sqlCommands = "";
            int noOfCreate = 0;
            int noOfAlter = 0;

            foreach (string sqlCommand in config.GenerateSchemaUpdateScript(
                    dialect, new NHibernate.Tool.hbm2ddl.DatabaseMetadata((DbConnection)Apollo.Application.CurrentSession.Connection, dialect)
                )
                .Where<string>(s => !s.StartsWith("create table v_") && !s.Contains("add constraint")))
            {
                sqlCommands += " \n" + sqlCommand;

                if (sqlCommand.StartsWith("create table"))
                    noOfCreate++;
                else
                    noOfAlter++;
            }

            if (sqlCommands == "")
            {
                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""No modification needed."" }");
                return;
            }

            try
            {
                NHibernate.ISession session = Apollo.Application.CurrentSession;

                NHibernate.ISQLQuery query = session.CreateSQLQuery(sqlCommands);

                query.ExecuteUpdate();

                context.Response.Write(@"{ ""status"": ""Done"", ""message"": """
                    + noOfCreate
                    + @" tables were created, <br />"
                    + noOfAlter
                    + @" tables were altered"" }");
            }
            catch (Exception ex)
            {
                context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action failed! Reason: " + ex.Message + "\" }");
            }
        }

        public static void GenerateEntityMappings(HttpContext context) {
            NHibernate.Cfg.Configuration config = Apollo.Application.Configuration;

            IList<Entity> entities = Apollo.Application.CurrentSession
                .QueryOver<Entity>()
                .List<Entity>();

            int noOfNewEntities = 0;
            int noOfNewProperties = 0;
            int noOfNewViews = 0;

            try
            {

                ISession session = Apollo.Application.CurrentSession;

                foreach (PersistentClass persistent in config.ClassMappings)
                {
                    ITransaction tran = null;

                    Entity entity = entities.FirstOrDefault<Entity>(en => en.Name == persistent.MappedClass.Name);

                    // check if the entity exists already
                    #region Create Entity Definition
                    if (entity == null)
                    {
                        string displayProperty = "";

                        if (persistent.PropertyIterator.Any<Property>(pi => pi.Name == "Name"))
                            displayProperty = "Name";
                        else if (persistent.PropertyIterator.Any<Property>(pi => pi.Name == "DisplayName"))
                            displayProperty = "DisplayName";
                        else if (persistent.PropertyIterator.Any<Property>(pi => pi.Name == "Description"))
                            displayProperty = "Description";
                        else displayProperty = persistent.IdentifierProperty.Name;

                        entity = new Entity()
                        {
                            Name = persistent.MappedClass.Name,
                            IdentifierProperty = persistent.IdentifierProperty.Name,
                            DisplayProperty = displayProperty,
                            Type = persistent.MappedClass.FullName
                        };


                        tran = session.BeginTransaction();
                        session.Save(entity);
                        tran.Commit();

                        LanguageDefinition entityDefinition = new LanguageDefinition()
                        {
                            DisplayName = entity.Name,
                            Type = "Entity",
                            Language = Apollo.Application.DefaultLanguage,
                            CodeHi = entity.Name
                        };
                        tran = session.BeginTransaction();
                        session.Save(entityDefinition);
                        tran.Commit();

                        noOfNewEntities++;
                    }
                    #endregion

                    // check if the are properties that don't exist
                    #region Create Properties Definitions
                    IList<EntityProperty> properties = Apollo.Application.CurrentSession
                                                            .QueryOver<EntityProperty>()
                                                            .Where(p => p.Entity.Id == entity.Id)
                                                            .List<EntityProperty>();

                    foreach (Property property in persistent.PropertyIterator)
                    {
                        EntityProperty eProperty = properties.FirstOrDefault(p => p.PropertyName == property.Name);

                        if (eProperty == null)
                        {
                            eProperty = new EntityProperty()
                            {
                                Entity = entity,
                                PropertyName = property.Name,
                                Type = property.IsEntityRelation ? EntityPropertyType.Relation : EntityPropertyType.Normal
                            };

                            tran = session.BeginTransaction();
                            session.Save(eProperty);
                            tran.Commit();

                            LanguageDefinition propertyDefinition = new LanguageDefinition()
                            {
                                DisplayName = eProperty.PropertyName,
                                Type = "EntityProperty",
                                Language = Apollo.Application.DefaultLanguage,
                                CodeHi = entity.Name,
                                CodeLo = eProperty.PropertyName
                            };
                            tran = session.BeginTransaction();
                            session.Save(propertyDefinition);
                            tran.Commit();

                            noOfNewProperties++;
                        }
                    }
                    #endregion

                    // check if the are views that don't exist
                    #region Create Entity View Definition
                    if (entity.DefaultView == null)
                    {
                        string viewString = persistent.IdentifierProperty.Name;

                        if (entity.DisplayProperty != viewString)
                        {
                            viewString += "," + entity.DisplayProperty;
                        }

                        int maxProperties = 3;
                        foreach (Property p in persistent.PropertyIterator)
                        {
                            if (p.Name == entity.DisplayProperty)
                                continue;

                            viewString += "," + p.Name;

                            maxProperties--;
                            if (maxProperties <= 0)
                                break;
                        }

                        tran = session.BeginTransaction();

                        EntityView view = new EntityView()
                        {
                            Entity = entity,
                            Name = entity.Name + " list",
                            ViewString = viewString
                        };
                        session.Save(view);

                        entity.DefaultView = view;
                        session.Update(entity);

                        tran.Commit();

                        noOfNewViews++;
                    }
                    #endregion
                }

                if(noOfNewEntities > 0 || noOfNewProperties > 0 || noOfNewViews > 0) 
                    context.Response.Write(@"{ ""status"": ""Done"", ""message"": """ + noOfNewEntities + @" entities mappings,<br /> " + noOfNewProperties + @" properties mappings,<br /> " + noOfNewViews + @" entity views"" }");
                else
                    context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""No modification needed."" }");
            }
            catch (Exception ex) {
                context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action failed! Reason: " + ex.Message + "\" }");
            }
        }

        public static void GenerateEntityRelations(HttpContext context)
        {
            IList<Entity> entities = Apollo.Application.CurrentSession
                .QueryOver<Entity>()
                .List<Entity>();
            IList<EntityProperty> properties = Apollo.Application.CurrentSession
                                                            .QueryOver<EntityProperty>()
                                                            .Where(p => p.Type == EntityPropertyType.Relation && p.BelongToEntity == null)
                                                            .List<EntityProperty>();

            try
            {
                foreach (EntityProperty property in properties)
                {
                    Entity belongsTo = entities.SingleOrDefault<Entity>(en => en.Name == property.Entity.MappedClass.GetProperty(property.PropertyName).PropertyType.Name);

                    ITransaction tran = Apollo.Application.CurrentSession.BeginTransaction();

                    property.BelongToEntity = belongsTo;
                    Apollo.Application.CurrentSession.Save(property);
                    tran.Commit();
                }

                if(properties.Count > 0)
                    context.Response.Write(@"{ ""status"": ""Done"", ""message"": """ + properties.Count + @" relations "" }");
                else
                    context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""No modification needed."" }");
            }
            catch (Exception ex)
            {
                context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action failed! Reason: " + ex.Message + "\" }");
            }
        }

        public static void CreateDefaultLanguage(HttpContext context)
        {
            // check if exists a default language
            if (Apollo.Application.CurrentSession
                    .QueryOver<Language>().Where(l => l.Culture == "en")
                    .List<Language>().Count > 0)
            {
                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""Already present."" }");
                return;
            }

            ISession session = Apollo.Application.CurrentSession;

            ITransaction tran = null;
            try
            {

                Language language = new Language() { Name = "English", Culture = "en", IsDefault = true };

                tran = session.BeginTransaction();
                session.Save(language);
                tran.Commit();

                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""English was created."" }");
            }
            catch (Exception ex)
            {
                if (tran != null)
                    tran.Rollback();

                context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action failed! Reason: " + ex.Message + "\" }");
            }
        }

        public static void CreateAdminUser(HttpContext context)
        {
            Role admin = Apollo.Application.CurrentSession.QueryOver<Role>()
                    .Where(r => r.Name == Role.ADMIN_ROLE)
                    .SingleOrDefault();

            if (admin == null) {
                admin = new Role() {
                    Name = Role.ADMIN_ROLE,
                    Code = Role.ADMIN_ROLE
                };

                Apollo.Application.CurrentSession.Save(admin);
            }

            User user = Apollo.Application.CurrentSession.QueryOver<User>()
                    .Where(u => u.Name == "admin")
                    .SingleOrDefault();

            if (user != null)
            {
                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""Already present."" }");
                return;
            }

            user = new User()
            {
                Name = "admin",
                Password = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile("1234", "MD5"),
                AuthenticationType = "Apollo",
                IsActive = true,
                Language = Apollo.Application.DefaultLanguage,
                LastLoginDate = DateTime.Now,
                InsertDate = DateTime.Now,
                InsertBy = "process.setup",
                Role = admin
            };

            Apollo.Application.CurrentSession.Save(user);

            context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""User/Pass: admin/1234 was created."" }");
        }

        public static void CreateAdvancedEntityManagement(HttpContext context) {
            Entity eEntity = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "Entity").SingleOrDefault();
            Entity eProperty = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "EntityProperty").SingleOrDefault();
            Entity eEntityView = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "EntityView").SingleOrDefault();
            Entity eEntityEdit = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "EntityEdit").SingleOrDefault();
            Entity eEntityFilter = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "EntityFilter").SingleOrDefault();
            
            
            EntityView evEntity = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                    .Where(ev => ev.Entity.Id == eEntity.Id).SingleOrDefault();

            EntityProperty epEntityProperty_Entity = Apollo.Application.CurrentSession.QueryOver<EntityProperty>()
                    .Where(ep => ep.Entity.Id == eProperty.Id && ep.PropertyName == "Entity").SingleOrDefault();
            EntityProperty epEntityView_Entity = Apollo.Application.CurrentSession.QueryOver<EntityProperty>()
                    .Where(ep => ep.Entity.Id == eEntityView.Id && ep.PropertyName == "Entity").SingleOrDefault();
            EntityProperty epEntityEdit_Entity = Apollo.Application.CurrentSession.QueryOver<EntityProperty>()
                    .Where(ep => ep.Entity.Id == eEntityEdit.Id && ep.PropertyName == "Entity").SingleOrDefault();
            EntityProperty epEntityFilter_Entity = Apollo.Application.CurrentSession.QueryOver<EntityProperty>()
                    .Where(ep => ep.Entity.Id == eEntityFilter.Id && ep.PropertyName == "Entity").SingleOrDefault();

            bool hasMadeModification = false;

            #region set Reverse Relations
            if (epEntityProperty_Entity.ReverseRelationItemView == null)
            {
                epEntityProperty_Entity.ReverseRelationItemView = evEntity;
                Apollo.Application.CurrentSession.Save(epEntityProperty_Entity);

                LanguageDefinition ldEntityProperty_Entity = new LanguageDefinition()
                {
                    CodeHi = "EntityProperty",
                    CodeLo = "Entity",
                    DisplayName = "Properties",
                    Language = Apollo.Application.DefaultLanguage,
                    Type = "ReverseProperty"
                };
                Apollo.Application.CurrentSession.Save(ldEntityProperty_Entity);

                hasMadeModification = true;
            }

            if (epEntityView_Entity.ReverseRelationItemView == null)
            {
                epEntityView_Entity.ReverseRelationItemView = evEntity;
                Apollo.Application.CurrentSession.Save(epEntityProperty_Entity);

                LanguageDefinition ldEntityView_Entity = new LanguageDefinition()
                {
                    CodeHi = "EntityView",
                    CodeLo = "Entity",
                    DisplayName = "Views",
                    Language = Apollo.Application.DefaultLanguage,
                    Type = "ReverseProperty"
                };
                Apollo.Application.CurrentSession.Save(ldEntityView_Entity);

                hasMadeModification = true;
            }

            if (epEntityEdit_Entity.ReverseRelationItemView == null) {
                epEntityEdit_Entity.ReverseRelationItemView = evEntity;
                Apollo.Application.CurrentSession.Save(epEntityEdit_Entity);

                LanguageDefinition ldEntityEdit_Entity = new LanguageDefinition()
                {
                    CodeHi = "EntityEdit",
                    CodeLo = "Entity",
                    DisplayName = "Edit forms",
                    Language = Apollo.Application.DefaultLanguage,
                    Type = "ReverseProperty"
                };
                Apollo.Application.CurrentSession.Save(ldEntityEdit_Entity);

                hasMadeModification = true;
            }

            if (epEntityFilter_Entity.ReverseRelationItemView == null)
            {
                epEntityFilter_Entity.ReverseRelationItemView = evEntity;
                Apollo.Application.CurrentSession.Save(epEntityFilter_Entity);

                LanguageDefinition ldEntityFilter_Entity = new LanguageDefinition()
                {
                    CodeHi = "EntityFilter",
                    CodeLo = "Entity",
                    DisplayName = "Filters by roles",
                    Language = Apollo.Application.DefaultLanguage,
                    Type = "ReverseProperty"
                };
                Apollo.Application.CurrentSession.Save(ldEntityFilter_Entity);

                hasMadeModification = true;
            }
            #endregion

            #region create Entity Forms
            if (eEntity.DefaultEdit == null) {
                EntityEdit eeEntity = new EntityEdit() { 
                    Entity = eEntity,
                    Name = "Entity edit form",
                    EditString = @"
<form entityname=""Entity"" entityid=""11"">
  <group type=""Properties"" displayname=""General"">
    <property col=""0"" row=""0"" type=""Normal"">Name</property>
    <property col=""1"" row=""0"" type=""ReadOnly"">Id</property>

    <property col=""0"" row=""1"" type=""Normal"">IdentifierProperty</property>
    <property col=""0"" row=""2"" type=""Normal"">DisplayProperty</property>
	<property col=""0"" row=""3"" type=""Normal"">Loading</property>

    <property col=""1"" row=""1"" type=""Normal"">Type</property>
    <property col=""1"" row=""2"" type=""Normal"">IconPath</property>

  </group>
  <group type=""Properties"" displayname=""Filtering"">
	<property col=""0"" row=""0"" type=""Normal"" customcontrol=""Filter"">Filter</property>
  </group>
  <group type=""Properties"" displayname=""Views &amp; Edit Forms"">
    <property col=""0"" row=""0"" type=""Normal"">DefaultView</property>
    <property col=""0"" row=""1"" type=""Normal"">DefaultEdit</property>
    <property col=""1"" row=""0"" type=""Normal"">CustomViewUrl</property>
    <property col=""1"" row=""1"" type=""Normal"">CustomEditUrl</property>
  </group>
</form>
"
                };

                Apollo.Application.CurrentSession.Save(eeEntity);

                eEntity.DefaultEdit = eeEntity;
                Apollo.Application.CurrentSession.Save(eEntity);

                hasMadeModification = true;
            }
            #endregion

            if(hasMadeModification)
                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""Created with success"" }");
            else
                context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""No modification needed."" }");
        }

        public static void GenerateSettingsMenus(HttpContext context)
        {
            // TODO check if menu exists :)

            // generate menu settings
            ITransaction tran = Apollo.Application.CurrentSession.BeginTransaction();
            try
            {
                MenuItem mi = new MenuItem()
                {
                    Name = "Settings",
                    IconPath = "/css/icons/icon_23.png"
                };

                Apollo.Application.CurrentSession.Save(mi);

                tran.Commit();

                tran = Apollo.Application.CurrentSession.BeginTransaction();

                Apollo.Application.CurrentSession.Save(new MenuItem() {
                    Name = "Users",
                    DisplayOrder = 1,
                    IconPath = "/css/icons/icon_62.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "User list").SingleOrDefault()
                });


                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Positions",
                    DisplayOrder = 2,
                    IconPath = "/css/icons/icon_74.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "Position list").SingleOrDefault()
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Roles",
                    DisplayOrder = 3,
                    IconPath = "/css/icons/icon_73.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "Role list").SingleOrDefault()
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Clear cache",
                    DisplayOrder = 0,
                    IconPath = "/css/icons/icon_39.png",
                    Parent = mi,
                    NavigateUrl = "/ClearCache.aspx"
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Database manager",
                    DisplayOrder = 7,
                    IconPath = "/css/icons/icon_40.png",
                    Parent = mi,
                    NavigateUrl = "/DatabaseManager.aspx"
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Entity manager",
                    DisplayOrder = 8,
                    IconPath = "/css/icons/icon_38.png",
                    Parent = mi,
                    NavigateUrl = "/EntityManager.aspx"
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Entity list",
                    DisplayOrder = 9,
                    IconPath = "/css/icons/icon_38.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "Entity list").SingleOrDefault()
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Language definition",
                    DisplayOrder = 10,
                    IconPath = "/css/icons/icon_75.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "LanguageDefinition list").SingleOrDefault()
                });

                Apollo.Application.CurrentSession.Save(new MenuItem()
                {
                    Name = "Menu manager",
                    DisplayOrder = 11,
                    IconPath = "/css/icons/icon_63.png",
                    Parent = mi,
                    EntityView = Apollo.Application.CurrentSession.QueryOver<EntityView>()
                        .Where(e => e.Name == "MenuItem list").SingleOrDefault()
                });


                tran.Commit();
            }
            catch (Exception ex) {
                context.Response.Write(@"{ ""status"": ""Failed"", ""message"": ""Action failed! Reason: " + ex.Message + "\" }");
            }

            context.Response.Write(@"{ ""status"": ""Done"", ""message"": ""Created with success"" }");
        }
    }

    public enum SetupAction { 
        Unknown = 0,
        CreateOrAlterTables = 2,
        GenerateEntityMappings = 3,
        GenerateEntityRelations = 4,
        CreateDefaultLanguage = 5,
        CreateAdvancedEntityManagement = 6,
        CreateUserManagement = 7,
        CreateAdminUser = 8,
        GenerateSettingsMenus = 9,
        ClearCache = 10
    }
}
